package b649a01.tomasulo;

import java.nio.ByteBuffer;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.Vector;

import b649a01.runners.sim_t;

/***
 * 
 * Class for constructing reservation stations or load/store buffers.
 * 
 * @author Chao Sun
 * @version 1.0
 * 
 */
public class rs_buf {
	private sim_t simulator;
	private int entry;
	private String operator;
	private Double vj, vk;
	private int qj, qk;

	private String source1, source2, target;
	private int s1Index, s2Index, tIndex, offset, memIndex;
	private rob_value_pair[] R, F;
	private LinkedList<rob> ROB;
	private Vector<rob_value_pair> CDB;

	/**
	 * Constructor
	 * 
	 * @param s
	 *          simulator
	 * @param e
	 *          entry number
	 * @param op
	 *          operator
	 * @param opT
	 *          operands
	 */
	public rs_buf(sim_t s, int e, String op, StringTokenizer opT) {
		simulator = s;
		entry = e;
		operator = op;
		vj = vk = null;
		qj = qk = -1;
		source1 = source2 = target = null;
		s1Index = s2Index = tIndex = offset = memIndex = -1;
		R = F = null;
		ROB = null;
		CDB = null;

		if (op.equals("ADD") || op.equals("SUB") || op.equals("MUL")
				|| op.equals("DIV")) { // integer arithmetic
			target = opT.nextToken();
			source1 = opT.nextToken();
			source2 = opT.nextToken();
			tIndex = Integer.parseInt(target.substring(1));
			s1Index = Integer.parseInt(source1.substring(1));
			s2Index = Integer.parseInt(source2.substring(1));
			// assign vj, vk, qj, qk
			R = simulator.getR();
			assignRegJ();
			assignRegK();
			// update register files
			simulator.setR(tIndex, null, e);
		}

		if (op.equals("L") || op.equals("LI") || op.equals("L.D")) { // load
			target = opT.nextToken();
			source1 = opT.nextToken();
			tIndex = Integer.parseInt(target.substring(1));
			if (op.equals("LI")) { // load from immediate value
				vj = (Double) (double) Integer.parseInt(source1);
				simulator.setR(tIndex, null, e);
			} else { // load from memory
				calOffset(source1);
				if (offset >= 0 && op.equals("L.D")) {
					loadMem(8);
					simulator.setF(tIndex, null, e);
				} else if (offset >= 0) {
					loadMem(4);
					simulator.setR(tIndex, null, e);
				}
			}
		}

		if (op.equals("S") || op.equals("S.D")) { // store
			source1 = opT.nextToken();
			s1Index = Integer.parseInt(source1.substring(1));
			if (op.equals("S")) {
				R = simulator.getR();
				assignRegJ();
			} else {
				F = simulator.getF();
				assignFRegJ();
			}
			target = opT.nextToken();
			calOffset(target);
		}

		if (op.equals("BEQ") || op.equals("BNEQ") || op.equals("BGTZ")
				|| op.equals("BLTZ")) { // branch
			source1 = opT.nextToken();
			s1Index = Integer.parseInt(source1.substring(1));
			R = simulator.getR();
			assignRegJ();
			if (operator.equals("BEQ") || operator.equals("BNEQ")) {
				source2 = opT.nextToken();
				s2Index = Integer.parseInt(source2.substring(1));
				assignRegK();
			}
			target = opT.nextToken();
			getBranchIndex();
			simulator.setBranch(offset);
		}

		// move from an integer register to a floating point register
		if (op.equals("MOVE")) {
			source1 = opT.nextToken();
			s1Index = Integer.parseInt(source1.substring(1));
			R = simulator.getR();
			assignRegJ();
			target = opT.nextToken();
			tIndex = Integer.parseInt(target.substring(1));
			simulator.setF(tIndex, null, e);
		}

		// double precision floating point arithmetic ops
		if (op.equals("ADD.D") || op.equals("SUB.D") || op.equals("MUL.D")
				|| op.equals("DIV.D")) {
			target = opT.nextToken();
			source1 = opT.nextToken();
			source2 = opT.nextToken();
			tIndex = Integer.parseInt(target.substring(1));
			s1Index = Integer.parseInt(source1.substring(1));
			s2Index = Integer.parseInt(source2.substring(1));
			F = simulator.getF();
			assignFRegJ();
			assignFRegK();
			simulator.setF(tIndex, null, e);
		}

		if (op.equals("BEQ.D") || op.equals("BNEQ.D") || op.equals("BGTZ.D")
				|| op.equals("BLTZ.D")) { // branch
			source1 = opT.nextToken();
			s1Index = Integer.parseInt(source1.substring(1));
			F = simulator.getF();
			assignFRegJ();
			if (operator.equals("BEQ.D") || operator.equals("BNEQ.D")) {
				source2 = opT.nextToken();
				s2Index = Integer.parseInt(source2.substring(1));
				assignFRegK();
			}
			target = opT.nextToken();
			getBranchIndex();
			simulator.setBranch(offset);
		}

		// move from a floating point register to an integer register
		if (op.equals("MOVE.D")) {
			source1 = opT.nextToken();
			s1Index = Integer.parseInt(source1.substring(1));
			F = simulator.getF();
			assignFRegJ();
			target = opT.nextToken();
			tIndex = Integer.parseInt(target.substring(1));
			simulator.setR(tIndex, null, e);
		}

	}

	private void assignRegJ() {
		if (R[s1Index].getROBEntry() == -1) {
			vj = R[s1Index].getValue();
		} else {
			qj = R[s1Index].getROBEntry();
			ROB = simulator.getROB();
			vj = simulator.monitorROB(qj);
			if (vj != null) {
				return;
			}
			vj = monitorCDB(qj);
		}
	}

	private void assignFRegJ() {
		if (F[s1Index].getROBEntry() == -1) {
			vj = F[s1Index].getValue();
		} else {
			qj = F[s1Index].getROBEntry();
			ROB = simulator.getROB();
			vj = simulator.monitorROB(qj);
			if (vj != null) {
				return;
			}
			vj = monitorCDB(qj);
		}
	}

	private void assignRegK() {
		if (R[s2Index].getROBEntry() == -1) {
			vk = R[s2Index].getValue();
		} else {
			qk = R[s2Index].getROBEntry();
			ROB = simulator.getROB();
			vk = simulator.monitorROB(qk);
			if (vk != null) {
				return;
			}
			vk = monitorCDB(qk);
		}
	}

	private void assignFRegK() {
		if (F[s2Index].getROBEntry() == -1) {
			vk = F[s2Index].getValue();
		} else {
			qk = F[s2Index].getROBEntry();
			ROB = simulator.getROB();
			vk = simulator.monitorROB(qk);
			if (vk != null) {
				return;
			}
			vk = monitorCDB(qk);
		}
	}

	private void calOffset(String s) {
		StringTokenizer sTokenizer = new StringTokenizer(s, "(");
		memIndex = Integer.parseInt(sTokenizer.nextToken()); // o
		source2 = sTokenizer.nextToken();
		if (!source2.startsWith("R")) {
			System.err.println("Address must be stored in an integer register");
			System.exit(1);
		}
		s2Index = Integer.parseInt(source2.substring(1, source2.length() - 1));
		R = simulator.getR();
		assignRegK();
		if (vk != null) { // register value available
			offset = vk.intValue() + memIndex;
		}
	}

	public boolean setOffset() {
		R = simulator.getR();
		if (vk == null) {
			return false;
		}
		offset = vk.intValue() + memIndex;
		if (offset < 0) {
			return false;
		} else {
			return true;
		}
	}

	public void loadMem(int n) {
		ROB = simulator.getROB();
		for (int i = 0; i < ROB.size(); i++) { // search ROB for dependency
			rob r = ROB.get(i);
			if (r.getDest() != null
					&& (r.getDest().equals(Integer.toString(offset)) || r.getDest()
							.equals(target))) {
				if (r.getStep() == 4) { // assign from ROB
					vj = r.getValue();
				} else if (r.getStep() == 3) { // from CDB
					vj = monitorCDB(r.getEntry());
				} else { // not ready to assign, keep qj
					qj = r.getEntry();
				}
				return;
			}
		}
		// no dependency, assign from memory
		byte dump[] = simulator.getDump();
		ByteBuffer buffer = ByteBuffer.allocate(n);
		buffer.put(dump, offset, n);
		if (n == 4) {
			vj = (double) buffer.getInt(0);
		} else {
			vj = buffer.getDouble(0);
		}
	}

	private Double monitorCDB(int entry) {
		CDB = simulator.getCDB();
		for (int i = 0; i < CDB.size(); i++) {
			rob_value_pair c = CDB.get(i);
			if (c.getROBEntry() == entry) {
				Double v = c.getValue();
				return v;
			}
		}
		return null;
	}

	private void getBranchIndex() {
		Hashtable<String, Integer> branchTable = simulator.getbranchTable();
		int searchflag = -2;
		if (!branchTable.containsKey(target)) {
			searchflag = simulator.searchBranch(target);
		}
		if (searchflag == -2) {
			offset = branchTable.get(target);
		} else if (searchflag == -1) {
			try {
				offset = (int) (Integer.parseInt(target) / 32);
			} catch (NumberFormatException ex) {
			}
		} else {
			offset = searchflag;
		}
	}

	public String getTarget() {
		return target;
	}

	public int getEntry() {
		return entry;
	}

	public String getOperator() {
		return operator;
	}

	public int getOffset() {
		return offset;
	}

	public Double getVj() {
		return vj;
	}

	public void setVj(Double o) {
		vj = o;
	}

	public Double getVk() {
		return vk;
	}

	public void setVk(Double o) {
		vk = o;
	}

	public int getQj() {
		return qj;
	}

	public int getQk() {
		return qk;
	}

}
